﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using AbstraX.Contracts;
using EnvDTE;
using System.Threading;
using Process = System.Diagnostics.Process;
using Debugger = System.Diagnostics.Debugger;
using System.Diagnostics;

namespace VSService
{
    [ServiceBehaviorAttribute(IncludeExceptionDetailInFaults=true)]
    public class VSService : IVSService
    {
        private static Dictionary<Guid, VSServiceSession> sessions = new Dictionary<Guid,VSServiceSession>();

        public List<VSServiceSession> GetSessions()
        {
            Monitor.Enter(sessions);

            var list = sessions.Values.Cast<VSServiceSession>().ToList();

            if (Process.GetProcesses().Where(p => p.ProcessName == "devenv").Any(p => !sessions.Values.Any(s => s.ProcessID == p.Id)))
            {
                var regularInstances = Process.GetProcesses().Where(p => p.ProcessName == "devenv" && !sessions.Values.Any(s => s.ProcessID == p.Id));

                regularInstances.ToList<Process>().ForEach(p => list.Add(new VSServiceSession(p)));
            }

            Monitor.Exit(sessions);

            return list;
        }

        public void OpenSession(Guid userID)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                throw new Exception(string.Format("Only one session allowed per user. UserID={0}", userID));
            }
            else
            {
                var session = new VSServiceSession(userID);

                session.SessionOpened = DateTime.Now;
                session.SessionDead += (deadSession) =>
                {
                    sessions.Remove(session.UserID);
                };

                sessions.Add(userID, session);
            }

            Monitor.Exit(sessions);
        }

        public void CloseSession(Guid userID)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                Monitor.Exit(sessions);

                session.Dispose();
                sessions.Remove(userID);

                return;
            }

            Monitor.Exit(sessions);
        }

        public void OpenVisualStudio(Guid userID)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.OpenVisualStudio();
                session.VSInstanceOpened = DateTime.Now;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
        }

        public void RegisterInstance(Guid userID, int processID)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.ProcessID = processID;
                session.VSInstanceRegistered = DateTime.Now;
                session.IsRegistered = true;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
        }

        public VSMessage GetMessage(Guid userID)
        {
            Debug.WriteLine("Call to GetMessage. Entering session");
            Monitor.Enter(sessions);
            Debug.WriteLine("Call to GetMessage. Entered session");

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                if (session.MessageQueue.Count > 0)
                {
                    var message = session.MessageQueue.Dequeue();

                    Monitor.Exit(sessions);
                    Debug.WriteLine("Call to GetMessage. Exited session, returning message: " + message.Message);

                    return message;
                }

                session.LastMessageRequest = DateTime.Now;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
            Debug.WriteLine("Call to GetMessage. Exited session, no messages");

            return null;
        }

        public void UnRegisterInstance(Guid userID, int processID)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

            }

            Monitor.Exit(sessions);
        }

        public VSSolution OpenVSSolution(Guid userID, string fileName)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.MessageQueue.Enqueue(new VSMessage(Messages.OpenSolution, fileName));

                session.LastMessage = DateTime.Now;
                session.CallEvent.Reset();

                Monitor.Exit(sessions);

                if (!session.CallEvent.WaitOne(1200000))
                {
                    Debugger.Break();
                }

                if (session.CallReturn == null)
                {
                    Debugger.Break();
                }

                return (VSSolution)session.CallReturn;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }
        }

        public void ReturnOpenVSSolution(Guid userID, VSSolution solution)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.Title = solution.Name;
                session.CallReturn = solution;
                session.CallEvent.Set();
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
        }

        public VSProject[] GetProjects(Guid userID, VSSolution solution)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.MessageQueue.Enqueue(new VSMessage(Messages.GetProjects));

                session.LastMessage = DateTime.Now;
                session.CallEvent.Reset();

                Monitor.Exit(sessions);

                if (!session.CallEvent.WaitOne(60000))
                {
                    Debugger.Break();
                }

                if (session.CallReturn == null)
                {
                    Debugger.Break();
                }

                return (VSProject[])session.CallReturn;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }
        }

        public void ReturnGetProjects(Guid userID, VSProject[] projects)
        {
            Monitor.Enter(sessions);

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.CallReturn = projects;
                session.CallEvent.Set();
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
        }

        public VSProjectItem[] GetModels(Guid userID, VSProject project)
        {
            Debug.WriteLine("Call to GetModels. Entering session");
            Monitor.Enter(sessions);
            Debug.WriteLine("Call to GetModels. Entered session");

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                session.MessageQueue.Enqueue(new VSMessage(Messages.GetModels, project.Name));

                session.LastMessage = DateTime.Now;
                session.CallEvent.Reset();

                Monitor.Exit(sessions);
                Debug.WriteLine("Call to GetModels. Exited session, waiting for return");

                if (!session.CallEvent.WaitOne(120000))
                {
                    Debugger.Break();
                }

                if (session.CallReturn == null)
                {
                    Debugger.Break();
                }

                Debug.WriteLine("Call to GetModels. Returning");
                return (VSProjectItem[])session.CallReturn;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }
        }

        public void ReturnGetModels(Guid userID, VSProjectItem[] models)
        {
            Debug.WriteLine("Call to ReturnGetModels. Entering session");
            Monitor.Enter(sessions);
            Debug.WriteLine("Call to ReturnGetModels. Entered session");

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                Debug.WriteLine("Call to ReturnGetModels. Setting event");
                session.CallReturn = models;
                session.CallEvent.Set();
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
            Debug.WriteLine("Call to ReturnGetModels. Exited session.");
        }

        public byte[] GetFileContents(Guid userID, VSProjectItem projectItem, int index)
        {
            Debug.WriteLine("Call to GetFileContents. Entering session");
            Monitor.Enter(sessions);
            Debug.WriteLine("Call to GetFileContents. Entered session");

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];
                
                session.MessageQueue.Enqueue(new VSMessage(Messages.GetFileContents, projectItem.FileNames[index]));

                session.LastMessage = DateTime.Now;
                session.CallEvent.Reset();

                Monitor.Exit(sessions);
                Debug.WriteLine("Call to GetFileContents. Exited session, waiting for return");

                if (!session.CallEvent.WaitOne(60000))
                {
                    Debugger.Break();
                }

                if (session.CallReturn == null)
                {
                    Debugger.Break();
                }

                Debug.WriteLine("Call to GetFileContents. Exited session, returning");
                return (byte[])session.CallReturn;
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }
        }

        public void ReturnGetFileContents(Guid userID, byte[] contents)
        {
            Debug.WriteLine("Call to ReturnGetFileContents. Entering session");
            Monitor.Enter(sessions);
            Debug.WriteLine("Call to ReturnGetFileContents. Entered session");

            if (sessions.ContainsKey(userID))
            {
                var session = sessions[userID];

                Debug.WriteLine("Call to ReturnGetFileContents. Setting event");
                session.CallReturn = contents;
                session.CallEvent.Set();
            }
            else
            {
                Monitor.Exit(sessions);
                throw new Exception(string.Format("Session for user does not exist. UserID={0}", userID));
            }

            Monitor.Exit(sessions);
            Debug.WriteLine("Call to ReturnGetFileContents. Exited session");
        }

        public void SendOutput(string[] output)
        {
        }

        public string[] GetOutput(int lineIndex)
        {
            return null;
        }

        public void SendStatus(string status)
        {
            Debug.WriteLine("Status:" + status);
        }

        public string GetStatus()
        {
            return null;
        }
    }
}
